﻿using System;
using ch06.binary;

namespace ch06.cmd
{
    public struct Dumper
    {
        private Module _module;
        private int _importedFuncCount;
        private int _importedTableCount;
        private int _importedMemCount;
        private int _importedGlobalCount;

        public static void Dump(Module module)
        {
            var d = new Dumper {_module = module};

            Console.Write("Version: 0x{0:x2}\n", d._module.Version);
            d.DumpTypeSec();
            d.DumpImportSec();
            d.DumpFuncSec();
            d.DumpTableSec();
            d.DumpMemSec();
            d.DumpGlobalSec();
            d.DumpExportSec();
            d.DumpStartSec();
            d.DumpElemSec();
            d.DumpCodeSec();
            d.DumpDataSec();
            d.DumpCustomSec();
        }

        private void DumpDataSec()
        {
            Console.Write("Data[{0:D}]:\n", _module.DataSec?.Length ?? 0);
            for (var i = 0; _module.DataSec != null && i < _module.DataSec.Length; i++)
            {
                Console.Write("  data[{0:D}]: mem={1:D}\n", i, _module.DataSec[i].Mem);
            }
        }

        private void DumpCodeSec()
        {
            Console.Write("Code[{0:D}]:\n", _module.CodeSec.Length);
            for (var i = 0; _module.CodeSec != null && i < _module.CodeSec.Length; i++)
            {
                Console.Write("  func[{0:D}]: locals=[", _importedFuncCount + i);
                if (_module.CodeSec[i].Locals.Length > 0)
                {
                    foreach (var locals in _module.CodeSec[i].Locals)
                    {
                        if (i > 0)
                        {
                            Console.Write(", ");
                        }

                        Console.Write("{0} x {1:D}",
                            Types.ValTypeToStr(locals.Type), locals.N);
                    }
                }

                Console.WriteLine("]");
                DumpExpr("    ", _module.CodeSec[i].Expr);
            }
        }

        private void DumpExpr(string indentation, Instruction[] expr)
        {
            foreach (var instr in expr)
            {
                switch (instr.Opcode)
                {
                    case OpCodes.Block:
                    case OpCodes.Loop:
                        var args0 = (BlockArgs) instr.Args;
                        var bt0 = _module.GetBlockType(args0.BT);
                        Console.Write("{0}{1} {2}\n", indentation, instr.GetOpname(), bt0);
                        DumpExpr(indentation + "  ", args0.Instrs);
                        Console.Write("{0}{1}\n", indentation, "end");
                        break;
                    case OpCodes.If:
                        var args1 = (IfArgs) instr.Args;
                        var bt1 = _module.GetBlockType(args1.BT);
                        Console.Write("{0}{1} {2}\n", indentation, "if", bt1);
                        DumpExpr(indentation + "  ", args1.Instrs1);
                        Console.Write("{0}{1}\n", indentation, "else");
                        DumpExpr(indentation + "  ", args1.Instrs2);
                        Console.Write("{0}{1}\n", indentation, "end");
                        break;
                    default:
                        if (instr.Args != null)
                        {
                            Console.Write("{0}{1} {2}\n", indentation, instr.GetOpname(), instr.Args);
                        }

                        break;
                }
            }
        }

        private void DumpElemSec()
        {
            Console.Write("Element[{0:D}]:\n", _module.ElemSec?.Length ?? 0);
            for (var i = 0; _module.ElemSec != null && i < _module.ElemSec.Length; i++)
            {
                Console.Write("  elem[{0:D}]: table{1:D}\n", i, _module.ElemSec[i].Table);
            }
        }

        private void DumpStartSec()
        {
            Console.Write("Start:\n");
            Console.Write("  func={0:D}\n", _module.StartSec);
        }

        private void DumpGlobalSec()
        {
            Console.Write("Global[{0:D}]:\n", _module.GlobalSec?.Length ?? 0);
            for (var i = 0; i < _module.GlobalSec?.Length; i++)
            {
                Console.Write("  global[{0:D}]: {1}\n",
                    _importedGlobalCount + i, _module.GlobalSec[i].Type);
            }
        }

        private void DumpExportSec()
        {
            Console.Write("Export[{0:D}]:\n", _module.ExportSec?.Length ?? 0);
            for (var i = 0; i < _module.ExportSec?.Length; i++)
            {
                switch (_module.ExportSec[i].Desc.Tag)
                {
                    case ConstData.ExportTagFunc:
                        Console.Write("  func[{0:D}]: name={1}\n", _module.ExportSec[i].Desc.Idx,
                            _module.ExportSec[i].Name);
                        break;
                    case ConstData.ExportTagTable:
                        Console.Write("  table[{0:D}]: name={1}\n", _module.ExportSec[i].Desc.Idx,
                            _module.ExportSec[i].Name);
                        break;
                    case ConstData.ExportTagMem:
                        Console.Write("  memory[{0:D}]: name={1}\n", _module.ExportSec[i].Desc.Idx,
                            _module.ExportSec[i].Name);
                        break;
                    case ConstData.ExportTagGlobal:
                        Console.Write("  global[{0:D}]: name={1}\n", _module.ExportSec[i].Desc.Idx,
                            _module.ExportSec[i].Name);
                        break;
                }
            }
        }

        private void DumpTypeSec()
        {
            Console.Write("Type[{0:D}]:\n", _module.TypeSec?.Length ?? 0);
            for (var i = 0; i < _module.TypeSec?.Length; i++)
            {
                Console.Write("  type[{0:D}]: {1}\n", i, _module.TypeSec[i]);
            }
        }

        private void DumpImportSec()
        {
            Console.Write("Import[{0:D}]:\n", _module.ImportSec?.Length ?? 0);
            if (_module.ImportSec == null) return;
            foreach (var imp in _module.ImportSec)
            {
                switch (imp.Desc.Tag)
                {
                    case ConstData.ImportTagFunc:
                        Console.Write("  func[{0:D}]: {1}.{2}, sig={3:D}\n",
                            _importedFuncCount, imp.Module, imp.Name, imp.Desc.FuncType);
                        _importedFuncCount++;
                        break;
                    case ConstData.ImportTagTable:
                        Console.Write("  table[{0:D}]: {1}.{2}, {3}\n",
                            _importedTableCount, imp.Module, imp.Name, imp.Desc.Table.Limits);
                        _importedTableCount++;
                        break;
                    case ConstData.ImportTagMem:
                        Console.Write("  memory[{0:D}]: {1}.{2}, {3}\n",
                            _importedMemCount, imp.Module, imp.Name, imp.Desc.Mem);
                        _importedMemCount++;
                        break;
                    case ConstData.ImportTagGlobal:
                        Console.Write("  global[{0:D}]: {1}.{2}, {3}\n",
                            _importedGlobalCount, imp.Module, imp.Name, imp.Desc.Global);
                        _importedGlobalCount++;
                        break;
                }
            }
        }

        private void DumpFuncSec()
        {
            Console.Write("Function[{0:D}]:\n", _module.FuncSec?.Length ?? 0);
            for (var i = 0; i < _module.FuncSec?.Length; i++)
            {
                Console.Write("  func[{0:D}]: sig={1:D}\n",
                    _importedFuncCount + i, _module.FuncSec[i]);
            }
        }

        private void DumpTableSec()
        {
            Console.Write("Table[{0:D}]:\n", _module.TableSec?.Length ?? 0);
            for (var i = 0; i < _module.TableSec?.Length; i++)
            {
                Console.Write("  table[{0:D}]: {1}\n",
                    _importedTableCount + i, _module.TableSec[i].Limits);
            }
        }

        private void DumpMemSec()
        {
            Console.Write("Memory[{0:D}]:\n", _module.MemSec?.Length ?? 0);
            for (var i = 0; i < _module.MemSec?.Length; i++)
            {
                Console.Write("  memory[{0:D}]: {1}\n",
                    _importedMemCount + i, _module.MemSec[i]);
            }
        }

        private void DumpCustomSec()
        {
            Console.Write("Custom[{0:D}]:\n", _module.CustomSecs?.Length ?? 0);
            for (var i = 0; i < _module.CustomSecs?.Length; i++)
            {
                Console.Write("  custom[{0:D}]: name={1}\n", i, _module.CustomSecs[i].Name);
            }
        }
    }
}